<?php
declare(strict_types=1);

namespace Zyucv7\Core\Filesystem;

use RuntimeException;

/**
 * 文件工具类 File
 *
 * @author Huangkai
 */
class File
{

    /**
     * 检查文件是否存在
     *
     * 检查指定路径的文件或目录是否存在。对于文件链接，会检查其指向的目标是否存在。
     *
     * @param string $fullPath 要检查的文件或目录的完整路径
     *
     * @return bool 文件或目录存在返回 true，否则返回 false
     */
    public function exists(string $fullPath): bool
    {
        return file_exists($fullPath);
    }
    
    /**
     * 读取文件全部内容
     *
     * @param string $fullPath 要读取的文件的「绝对路径」或「相对于当前工作目录的路径」，
     *                         必须包含扩展名，例如：/var/www/data/report.csv
     *
     * @return string 文件完整内容，二进制安全（binary-safe）
     *
     * @throws RuntimeException 文件不存在时抛出
     * @throws RuntimeException 读取失败时抛出（如权限不足、文件被锁住等）
     */
    public function read(string $fullPath): string
    {
        if (!is_file($fullPath)) {
            throw new RuntimeException("File not found: {$fullPath}");
        }

        $content = file_get_contents($fullPath);
        if ($content === false) {
            throw new RuntimeException("Failed to read file: {$fullPath}");
        }

        return $content;
    }

    /**
     * 将数据写入指定文件
     *
     * @param string $fullPath 目标文件的完整路径，含扩展名，例如：/var/www/log/debug.log
     * @param string $data     待写入的字符串，二进制安全
     * @param int    $flags    位掩码，支持 FILE_APPEND、LOCK_EX 等，默认 0（覆盖）
     *
     * @return int 实际写入的字节数，可用于校验是否完整写入
     *
     * @throws RuntimeException 创建目录失败时抛出
     * @throws RuntimeException 写入失败时抛出（如磁盘已满、权限不足等）
     */
    public function write(string $fullPath, string $data, int $flags = 0): int
    {
        $dir = dirname($fullPath);
        if (!is_dir($dir)) {
            // 找不到目录，直接抛异常
            throw new RuntimeException("Can not find dir: {$dir}");
        }

        $bytes = file_put_contents($fullPath, $data, $flags);
        if ($bytes === false) {
            throw new RuntimeException("Failed to write file: {$fullPath}");
        }

        return $bytes;
    }

    /**
     * 删除单个文件
     *
     * 仅对「普通文件」生效，不会删除目录。若文件不存在，方法返回 false，
     * 删除成功返回 true；若删除操作被系统拒绝，将抛出异常。
     *
     * @param string $fullPath 待删除文件的完整路径
     *
     * @return bool 文件不存在时返回 false；成功删除返回 true
     *
     * @throws RuntimeException 删除失败时抛出（如文件被占用、权限不足等）
     */
    public function delete(string $fullPath): bool
    {
        if (!is_file($fullPath)) {
            return false;
        }

        if (!unlink($fullPath)) {
            throw new RuntimeException("Failed to delete file: {$fullPath}");
        }

        return true;
    }

    /**
     * 获取文件大小
     *
     * 返回文件的字节数，最大支持 2^63-1（PHP 内部使用 64 位有符号整）。
     *
     * @param string $fullPath 目标文件的完整路径
     *
     * @return int 文件大小，单位字节（bytes）
     *
     * @throws RuntimeException 文件不存在时抛出
     */
    public function size(string $fullPath): int
    {
        if (!is_file($fullPath)) {
            throw new RuntimeException("File not found: {$fullPath}");
        }

        return filesize($fullPath);
    }

    /**
     * 获取文件 MIME 类型
     *
     * 使用 fileinfo 扩展检测真实内容类型，而非简单依赖扩展名。
     * 若扩展未启用，会抛出 RuntimeException；若检测失败，返回默认二进制类型。
     *
     * @param string $fullPath 目标文件的完整路径
     *
     * @return string MIME 类型，例如：text/plain、image/jpeg
     *
     * @throws RuntimeException 文件不存在时抛出
     * @throws RuntimeException fileinfo 扩展无法打开时抛出
     */
    public function mime(string $fullPath): string
    {
        if (!is_file($fullPath)) {
            throw new RuntimeException("File not found: {$fullPath}");
        }

        $info = finfo_open(FILEINFO_MIME_TYPE);
        if ($info === false) {
            throw new RuntimeException("Failed to open fileinfo");
        }

        $mime = finfo_file($info, $fullPath);
        finfo_close($info);

        return $mime ?: 'application/octet-stream';
    }

    /**
     * 获取文件最后修改时间
     *
     * 返回 Unix 时间戳（秒），可用于缓存失效判断、增量同步等场景。
     *
     * @param string $fullPath 目标文件的完整路径
     *
     * @return int Unix 时间戳，单位秒
     *
     * @throws RuntimeException 文件不存在时抛出
     */
    public function modTime(string $fullPath): int
    {
        if (!is_file($fullPath)) {
            throw new RuntimeException("File not found: {$fullPath}");
        }

        return filemtime($fullPath);
    }

    /**
     * 判断文件是否可读
     *
     * 同时校验「文件存在」与「可读权限」，比单纯调用 is_readable() 更严谨。
     *
     * @param string $fullPath 目标文件的完整路径
     *
     * @return bool 可读返回 true，否则返回 false
     */
    public function isReadable(string $fullPath): bool
    {
        return is_file($fullPath) && is_readable($fullPath);
    }

    /**
     * 判断文件是否可写
     *
     * 注意：在 Linux 下，若父目录设有 sticky 位或 ACL，仍可能写入失败，
     * 此时建议捕获 write() 抛出的异常做最终校验。
     *
     * @param string $fullPath 目标文件的完整路径
     *
     * @return bool 可写返回 true，否则返回 false
     */
    public function isWritable(string $fullPath): bool
    {
        return is_file($fullPath) && is_writable($fullPath);
    }

    /**
     * 获取文件扩展名
     *
     * 使用 pathinfo() 提取，返回小写形式，不含前导点。例如：jpg、csv、gz
     *
     * @param string $fullPath 目标文件的完整路径
     *
     * @return string 文件扩展名，小写；若文件无扩展名则返回空字符串
     */
    public function extension(string $fullPath): string
    {
        return strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));
    }

    /**
     * 获取文件基础名称（含扩展名）
     *
     * 例如：/var/www/demo.txt 返回 demo.txt
     *
     * @param string $fullPath 目标文件的完整路径
     *
     * @return string 文件基础名称
     */
    public function basename(string $fullPath): string
    {
        return basename($fullPath);
    }

    /**
     * 获取文件所在目录路径
     *
     * 例如：/var/www/demo.txt 返回 /var/www
     * 失败时返回 '.'（极罕见，通常不会发生）
     *
     * @param string $fullPath 目标文件的完整路径
     *
     * @return string 目录路径，不含末尾斜杠
     */
    public function dirname(string $fullPath): string
    {
        return dirname($fullPath);
    }
}